﻿(function() {
    nova.ScrollerBase = function (selector) {
        this.selector = selector;
        this.direction = nova.ScrollerBase.directions.vertical;
        this.horizontalPositions = [];
        this.verticalPositions = [];
        this.hasVerticalParent = false;
        this.hasHorizontalParent = false;
        this.isBounceable = true;
        this.bounceRate = 0.5;
        this.formFields = "input,select,textarea";

        this._$scroller = null;
        this._$content = null;
        this._scrollerHeight = 0;
        this._scrollerWidth = 0;
        this._lastTouchPosition = null;
        this._startDirection = null;
        this._isScrollingCancelled = false;
        this._lastVerticalDistance = 0;
        this._lastHorizontalDistance = 0;
    };

    nova.ScrollerBase.directions = {
        horizontal: "horizontal",
        vertical: "vertical"
    };

    nova.ScrollerBase.prototype.init = function () {
        var obj = this;
        obj._$scroller = $(obj.selector);
        obj._$scroller.addClass('scroller');
        if (nova.application.isOverflowScrollSupported()) {
            var scrollAttr = 'overflow-y';
            if (obj.direction == nova.ScrollerBase.directions.horizontal) {
                scrollAttr = 'overflow-x';
            }
            obj._$scroller.css(scrollAttr, 'scroll');
            return;
        }
        obj._$content = obj._$scroller.children('.scroller-content');
        if (obj._$content.length == 0) {
            obj._$scroller.wrapInner('<div class="scroller-content"></div>');
            obj._$content = obj._$scroller.children();
        }
        var events = nova.touch.eventNames;
        obj._$scroller[0].addEventListener(events.touchstart, function (e) {
            e.preventDefault();
            obj._handleTouchstart(e);
        });
        obj._$scroller[0].addEventListener(events.touchmove, function (e) {
            obj._handleTouchmove(e);
        });
        obj._$scroller[0].addEventListener(events.touchend, function (e) {
            obj._handleTouchend(e);
        });

        var $fields = obj._$scroller.find(obj.formFields);
        if ($fields.length > 0) {
            $fields.each(function () {
                this.addEventListener(events.touchstart, function (e) {
                    e.stopPropagation();
                });
                this.addEventListener(events.touchmove, function (e) {
                    e.stopPropagation();
                });
                this.addEventListener(events.touchend, function (e) {
                    e.stopPropagation();
                });
            });
        }
    };

    nova.ScrollerBase.prototype.isHorizontal = function () {
        return this.direction == nova.ScrollerBase.directions.horizontal;
    };

    nova.ScrollerBase.prototype.isVertical = function () {
        return this.direction == nova.ScrollerBase.directions.vertical;
    };

    nova.ScrollerBase.prototype.setContentPosition = function (position) {
        var p = position || { left: 0, top: 0 };
        this._$content.css("left", p.x + "px").css("top", p.y + "px");
    };

    nova.ScrollerBase.prototype.scrollTo = function (top, left) {
        if (this._$content == null) {
            return;
        }
        var paras = {};
        if (top != null) {
            paras.top = top;
        }
        if (left != null) {
            paras.left = left;
        }
        this._$content.animate(paras, 'fast');
    };

    nova.ScrollerBase.prototype._handleTouchstart = function (e) {
        var obj = this;
        obj._scrollerHeight = obj._$scroller.height();
        obj._scrollerWidth = obj._$scroller.width();
        obj._lastTouchPosition = nova.touch.getTouchPosition(e);
        obj._startDirection = null;
        obj._isScrollingCancelled = false;
        obj._lastVerticalDistance = 0;
        obj._lastHorizontalDistance = 0;
    };

    nova.ScrollerBase.prototype._handleTouchmove = function (e) {
        var obj = this;
        if (obj._isScrollingCancelled) {
            return;
        }
        if (obj._hasSameDirectionParent()) {
            e.stopPropagation();
        }
        var position = nova.touch.getTouchPosition(e);
        obj._setStartDirection(position);
        obj._setArgsCancelledOrStopped(e);
        if (obj._isScrollingCancelled) {
            return;
        }
        var contentPosition = obj._getNextContentPosition(position);
        obj.setContentPosition(contentPosition);
        obj._lastTouchPosition = position;
    };

    nova.ScrollerBase.prototype._handleTouchend = function (e) {
        this._lastVerticalDistance *= 10;
        this._lastHorizontalDistance *= 10;
        this._slowDown();
    };

    nova.ScrollerBase.prototype._getContentPosition = function () {
        var contentPosition = this._$content.position();
        return { x: contentPosition.left, y: contentPosition.top };
    };

    nova.ScrollerBase.prototype._slowDown = function () {
        var obj = this;
        var contentPosition = this._getContentPosition();
        var nextPosition = this._getSlowDownPosition(contentPosition);
        this.setContentPosition(nextPosition);
        if (this._lastVerticalDistance != 0 || this._lastHorizontalDistance != 0) {
            setTimeout(function () {
                obj._slowDown();
            }, 20);
        }
    };

    nova.ScrollerBase.prototype._hasSameDirectionParent = function () {
        return (this.hasVerticalParent && this.direction == nova.ScrollerBase.directions.vertical)
            || (this.hasHorizontalParent && this.direction == nova.ScrollerBase.directions.horizontal);
    };

    nova.ScrollerBase.prototype._setStartDirection = function (position) {
        var obj = this;
        if (obj._startDirection == null) {
            if (obj._lastTouchPosition != null) {
                var isVerticalScrolling = Math.abs(position.y - obj._lastTouchPosition.y)
                    > Math.abs(position.x - obj._lastTouchPosition.x);
                obj._startDirection = isVerticalScrolling ? nova.ScrollerBase.directions.vertical : nova.ScrollerBase.directions.horizontal;
            }
        }
    };

    nova.ScrollerBase.prototype._setArgsCancelledOrStopped = function (e) {
        var obj = this;
        if (obj.hasHorizontalParent && obj.isVertical() && obj._startDirection == nova.ScrollerBase.directions.horizontal) {
            obj._isScrollingCancelled = true;
            return;
        }
        if (obj.hasVerticalParent && obj.isHorizontal() && obj._startDirection == nova.ScrollerBase.directions.vertical) {
            obj._isScrollingCancelled = true;
            return;
        }
        if (obj.hasHorizontalParent && obj.isVertical() && obj._startDirection == nova.ScrollerBase.directions.vertical) {
            e.stopPropagation();
        }
        if (obj.hasVerticalParent && obj.isHorizontal() && obj._startDirection == nova.ScrollerBase.directions.horizontal) {
            e.stopPropagation();
        }
    };

    nova.ScrollerBase.prototype._getBoundary = function () {
        var boundary = {
            topLeft: {
                x: 0,
                y: 0
            },
            bottomRight: {
                x: 0,
                y: 0
            }
        };
        if (this.isVertical()) {
            boundary.topLeft.y = this._$scroller.height() - this._$content.height();
        }
        if (this.isHorizontal()) {
            boundary.topLeft.x = this._$scroller.width() - this._$content.width();
        }
        if (boundary.topLeft.y > 0) {
            boundary.topLeft.y = 0;
        }
        if (boundary.topLeft.x > 0) {
            boundary.topLeft.x = 0;
        }
        return boundary;
    };

    nova.ScrollerBase.prototype._adjustPositionToBoundary = function (nextPosition, contentPosition, boundary) {
        var obj = this;
        if (obj.isHorizontal()) {
            if (nextPosition.x > boundary.bottomRight.x || nextPosition.x < boundary.topLeft.x) {
                if (obj.isBounceable) {
                    nextPosition.x = contentPosition.x + (nextPosition.x - contentPosition.x) * obj.bounceRate;
                } else {
                    nextPosition.x = contentPosition.x;
                }
            }
        }
        if (obj.isVertical()) {
            if (nextPosition.y > boundary.bottomRight.y || nextPosition.y < boundary.topLeft.y) {
                if (obj.isBounceable) {
                    nextPosition.y = contentPosition.y + (nextPosition.y - contentPosition.y) * obj.bounceRate;
                } else {
                    nextPosition.y = contentPosition.y;
                }
            }
        }
    };

    nova.ScrollerBase.prototype._getSlowDownPosition = function (contentPosition) {
        var boundary = this._getBoundary();
        var minDistance = 6;
        var nextPosition = {
            x: this.isHorizontal() ? contentPosition.x + this._lastHorizontalDistance * this.bounceRate : contentPosition.x,
            y: this.isVertical() ? contentPosition.y + this._lastVerticalDistance * this.bounceRate : contentPosition.y
        };
        if (this.isVertical()) {
            if (nextPosition.y < boundary.topLeft.y) {
                nextPosition.y = boundary.topLeft.y + (contentPosition.y - boundary.topLeft.y) * this.bounceRate;
                if (Math.abs(nextPosition.y - boundary.topLeft.y) < minDistance) {
                    nextPosition.y = boundary.topLeft.y;
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            } else if (nextPosition.y > boundary.bottomRight.y) {
                nextPosition.y = boundary.bottomRight.y + (contentPosition.y - boundary.bottomRight.y) * this.bounceRate;
                if (Math.abs(nextPosition.y - boundary.bottomRight.y) < minDistance) {
                    nextPosition.y = boundary.bottomRight.y;
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            } else {
                if (Math.abs(nextPosition.y - contentPosition.y) < minDistance) {
                    this._lastVerticalDistance = 0;
                } else {
                    this._lastVerticalDistance = nextPosition.y - contentPosition.y;
                }
            }
        }
        if (this.isHorizontal()) {
            if (nextPosition.x < boundary.topLeft.x) {
                nextPosition.x = boundary.topLeft.x + (contentPosition.x - boundary.topLeft.x) * this.bounceRate;
                if (Math.abs(nextPosition.x - boundary.topLeft.x) < minDistance) {
                    nextPosition.x = boundary.topLeft.x;
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            } else if (nextPosition.x > boundary.bottomRight.x) {
                nextPosition.x = boundary.bottomRight.x + (contentPosition.x - boundary.bottomRight.x) * this.bounceRate;
                if (Math.abs(nextPosition.x - boundary.bottomRight.x) < minDistance) {
                    nextPosition.x = boundary.bottomRight.x;
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            } else {
                if (Math.abs(nextPosition.x - contentPosition.x) < minDistance) {
                    this._lastHorizontalDistance = 0;
                } else {
                    this._lastHorizontalDistance = nextPosition.x - contentPosition.x;
                }
            }
        }
        return nextPosition;
    };

    nova.ScrollerBase.prototype._getNextContentPosition = function (touchPosition) {
        var obj = this;
        var boundary = obj._getBoundary();
        var hDistance = touchPosition.x - obj._lastTouchPosition.x;
        var vDistance = touchPosition.y - obj._lastTouchPosition.y;
        var contentPosition = this._getContentPosition();
        var nextPosition = {
            x: obj.isHorizontal() ? contentPosition.x + hDistance : contentPosition.x,
            y: obj.isVertical() ? contentPosition.y + vDistance : contentPosition.y
        };
        obj._adjustPositionToBoundary(nextPosition, contentPosition, boundary);
        obj._lastVerticalDistance = nextPosition.y - contentPosition.y;
        obj._lastHorizontalDistance = nextPosition.x - contentPosition.x;
        return nextPosition;
    };
})();


